/*
 * @Author: LibraStalker 1299640057@qq.com
 * @Date: 2023-01-27 23:44:51
 * @FilePath: base.ts
 * @Description: 所有检测类的基类
 */
import {Runner} from '@paddlejs/paddlejs-core';
import {FeedShape, ModelConfig} from "../commons/interface";

/**
 * 抽象类，继承此类以实现人脸框检测的模型、人脸关键点检测的模型，以后可以扩展实现人脸表情检测的模型等
 *
 * 注意！！！子类需要重写构造函数，在其中调用\_\_init_\_，否则\_\_init\_\_不会调用，导致模型配置为空。
 */
export abstract class ModelSDK {
    // 默认模型输入形状
    abstract readonly defaultFeedShape: FeedShape;
    // 实现此属性来表示默认模型配置
    abstract readonly defaultModelConfig: ModelConfig;
    // 如果不用只读属性就得用深复制防止默认模型配置被无意修改，用只读属性的话只要不是故意修改就没问题。
    // 由于模型配置只在初始化时用到，因此如果需要更改配置，就直接重新实例化一个类吧。
    modelConfig: ModelConfig;
    feedShape: FeedShape;
    // Runner是运行模型的核心
    runner: Runner;

    // 注意！！！子类需要重写构造函数，在其中调用\_\_init_\_，否则\_\_init\_\_不会调用，导致模型配置为空。
    protected constructor(modelConfig?: ModelConfig) {
        this.modelConfig = modelConfig;
    }

    //
    // 这部分逻辑如果直接放在此抽象类的构造函数中，访问到的默认模型配置是抽象类的默认模型配置，因为是先执行父类的构造函数再将子类的属性初始化并赋值。
    // 也不能把\_\_init\_\_放在构造函数中，否则还是访问到抽象类的属性。
    // 但这样的缺点是子类需要重写构造函数，在其中调用\_\_init_\_，否则\_\_init\_\_不会调用。
    /**
     * 将传入的模型配置覆盖默认的模型配置中已经有的部分，其他部分仍采用默认配置。
     */
    protected __init__() {
        // this.modelConfig只在这一处地方通过浅复制修改，从而保证它不会改变默认模型配置的深层属性
        this.modelConfig = Object.assign({}, this.defaultModelConfig, this.modelConfig); // 浅复制
        this.feedShape = Object.assign({}, this.defaultFeedShape, this.modelConfig?.feedShape); // 浅复制
    }

    // 用模型配置初始化Runner
    async init(): Promise<any> {
        this.runner = new Runner(this.modelConfig);
        return this.runner.init();
        // return this;
    }

    /**
     * RGBA转灰度
     * @param image_array (H, W, RGBA), dtype=uint8，元素取值为0-255的 H * W * 4 的数组，通过ImageData.data获得
     * @param image_array.length H * W * 4
     * @return (H, W, 1), dtype=uint8，元素取值为0-255的 H * W * 1 数组
     */
    RGBAtoGRAY(image_array: Uint8ClampedArray): Uint8ClampedArray {
        // 转为单通道，长度为image_array的1/4
        const result = new Uint8ClampedArray(image_array.length / 4);
        let offset = 0;
        for (let i = 0; i < image_array.length; i += 4, offset++) {
            // 下面这行参考https://www.cnblogs.com/zhangjiansheng/p/6925722.html
            result[offset] = (image_array[i] * 38 + image_array[i + 1] * 75 + image_array[i + 2] * 15) >> 7;
        }
        return result;
    }

    /**
     * 计算数组中元素的总和
     * @param image_array
     * @return 整数
     */
    calculate_sum(image_array: Uint8ClampedArray): number {
        return image_array.reduce((a, b) => a + b);
    }

    /**
     * 计算数组中元素的平均值，如果给出sum就利用sum来计算，否则重新计算一遍sum
     * @param image_array
     * @param sum 已经计算好的元素的总和
     * @return 浮点数
     */
    calculate_mean(image_array: Uint8ClampedArray, sum?: number): number {
        return (sum ?? this.calculate_sum(image_array)) / image_array.length;
    }

    /**
     * 计算数组中元素的标准差，如果给出mean就利用mean来计算，否则重新计算一遍mean，并把sum传给this.calculate_mean
     * @param image_array
     * @param mean 已经计算好的元素的平均值
     * @param sum 已经计算好的元素的总和
     * @return 非负浮点数
     */
    calculate_std(image_array: Uint8ClampedArray, mean?: number, sum?: number): number {
        return Math.sqrt(image_array.reduce((a, b) =>
            a + (b - (mean ?? this.calculate_mean(image_array, sum))) ** 2) / image_array.length);
    }

    // 使用模型来检测，应包含前处理和后处理的逻辑
    abstract detect(...args);
}